package edu.umb.cs.antmanager.manager;

import static edu.umb.cs.antmanager.common.Constants.ANT_MANAGER_HOME;

import java.io.File;
import java.io.IOException;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Vector;

import org.apache.xmlbeans.XmlException;

import edu.umb.cs.antmanager.domain.MachineType;
import edu.umb.cs.antmanager.domain.MachinesDocument;
import edu.umb.cs.antmanager.domain.TestType;

public class ResourceManager {

	private HashMap<String, Integer> resourceCountMap;
	private int maxCount;
	private static ResourceManager resourceManager;
	private MachineType[] machines;
	
	public MachineType[] getMachines() {
		return machines;
	}



	public static synchronized ResourceManager getResourceManager(){
		if(resourceManager == null) resourceManager = new ResourceManager();
		return resourceManager;
	}
	
	

	/**
	 * Manage all the resources the system has. Analyze a resource requirement
	 * and choose the best machine from a list of idle machines.
	 * 
	 */
	private ResourceManager() {
		try{
			MachinesDocument machinesDoc = MachinesDocument.Factory
			.parse(new File(ANT_MANAGER_HOME+"\\"+"machines.xml"));
			machines = machinesDoc.getMachines()
			.getMachineArray();
			resourceCountMap = new HashMap<String, Integer>();
			maxCount = 1;
			for (MachineType m : machines)
				if (m.getResources() != null)
					for (String res : m.getResources().split(","))
						if (resourceCountMap.containsKey(res)) {
							resourceCountMap
									.put(res, resourceCountMap.get(res) + 1);
							if (resourceCountMap.get(res) > maxCount)
								maxCount = resourceCountMap.get(res);
						} else
							resourceCountMap.put(res, 1);
		} catch (XmlException e) {
			// TODO the machines.xml's format is incorrect
			System.err.println("machines.xml's format is incorrect");
		} catch (IOException e) {
			// TODO the machines.xml is not accessible or host name is
			// unavailable
			System.err.println("Cannot found machines.xml!");
		}
	}

	/**
	 * Find the best machine to fulfill the requirement
	 * 
	 * @param requirement
	 *            - The resources requirement of the target
	 * @param machines
	 *            - List of idle machines
	 * @return the best machine to fulfill the requirement. if no match found
	 *         return null
	 */
	public MachineType findBestMachine(String requirement,
			List<MachineType> machines) {
		MachineType bestMachine = null;
		int best = 0;
		for (MachineType m : machines) {
			int matchResult = resourceMatch(requirement, m.getResources());
			if (matchResult == 0)
				return m;
			if (matchResult > 0)
				if (best == 0 || matchResult < best) {
					best = matchResult;
					bestMachine = m;
				}
		}
		return bestMachine;
	}

	/**
	 * Compare target resources requirement with the resources the machine has
	 * 
	 * @param requirement
	 *            - The resources requirement of the target
	 * @param resource
	 *            - The resources the machine has
	 * @return <0 if match fails, 0 if match exactly, >0 otherwise.
	 */
	public int resourceMatch(String requirement, String resource) {
		// requirement or resource could be null
		if (requirement == null)
			requirement = "";
		if (resource == null)
			resource = "";

		Vector<String> reqList = new Vector<String>(Arrays.asList(requirement
				.split(",")));
		Vector<String> resList = new Vector<String>(Arrays.asList(resource
				.split(",")));

		Vector<String> shareList = new Vector<String>();

		// remove all the empty resource name in the list
		while (reqList.contains("") || resList.contains("")) {
			reqList.remove("");
			resList.remove("");
		}

		for (String req : reqList)
			if (resList.contains(req))
				shareList.add(req);

		for (String req : shareList) {
			resList.remove(req);
			reqList.remove(req);
		}

		if (reqList.size() == 0)
			return evaluateResourceWeight(resList);
		else
			return -reqList.size();
	}

	/**
	 * Evaluate a resource list's weight base on the total resources the system
	 * has. The higher the weight value is the more important those resources
	 * are.
	 * 
	 * @param resList
	 *            - A list of resource
	 * @return weight value
	 */
	private int evaluateResourceWeight(List<String> resList) {
		int weightValue = 0;
		for (String res : resList) {
			weightValue += Math.pow((maxCount - resourceCountMap.get(res) + 1),
					2);
		}
		return weightValue;
	}

	/**
	 * Find the best test for a machine
	 * 
	 * @param untestedTestList
	 *            - available test list
	 * @param resources
	 *            - the resource the machine has
	 * @return The best test for the machine
	 */

	public TestType findBestTest(List<TestType> untestedTestList,
			String resources) {
		TestType bestTest = null;
		int best = 0;
		for (TestType t : untestedTestList) {
			int matchResult = resourceMatch(t.getRequirements(), resources);
			if (matchResult == 0)
				return t;
			if (matchResult > 0)
				if (best == 0 || matchResult < best) {
					best = matchResult;
					bestTest = t;
				}
		}
		return bestTest;
	}
}
